package com.yhb.channel.impl.ANYPAY;

import com.yhb.channel.ChannelConstant;
import com.yhb.channel.ChannelFactory;
import com.yhb.channel.PayChannel;
import com.yhb.channel.dao.ANYPAY.DHL09.AnyPayChannelBoundRelationshipDHL09Mapper;
import com.yhb.channel.dao.ANYPAY.DHL09.AnyPayChannelBoundRelationshipDHL09Record;
import com.yhb.channel.dao.flow.ExecutionFlow;
import com.yhb.channel.exception.CardAlreadyBoundException;
import com.yhb.channel.exception.PayChannelDeadException;
import com.yhb.channel.exception.RemitCardUnboundException;
import com.yhb.channel.exception.TryingBindCardException;
import com.yhb.channel.impl.ANYPAY.http.AnyPayHttpClient;
import com.yhb.channel.impl.ANYPAY.http.response.*;
import com.yhb.channel.impl.ANYPAY.http.utils.AnyPayRandomUtil;
import com.yhb.channel.model.*;
import com.yhb.channel.provider.ChannelBaseService;
import com.yhb.channel.provider.INotifyProvider;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import lombok.extern.log4j.Log4j;
import lombok.var;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.TextUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Log4j
@Service
public class AnyPayDHL09Channel implements PayChannel {
    /* ------------配置------------ */
    @Value("${config.url}")
    private String baseUrl;

    /* ------------常量------------ */
    public static final String CHANNEL_NAME = "深圳安派";
    public static final String CHANNEL_TYPE = ChannelFactory.ANYPAY_DHL09;
    public static final String PRODUCT_CATEGORY = "DHL";
    public static final String BINDCARD_SMS_PREFIX = CHANNEL_TYPE + "_BINDCARD_";

    /* ------------Base Provider Beans or utils------------ */
    @Autowired
    private ChannelBaseService channelBaseService;

    private ChannelLogger logger;

    /* ------------AnyPay Provider Beans or utils------------ */
    @Autowired
    private AnyPayHttpClient httpClient;

    @Autowired
    private AnyPayChannelBoundRelationshipDHL09Mapper db;

    /* -----------启动方法---------- */
    @PostConstruct
    private void init() {
        logger = new ChannelLogger(CHANNEL_NAME + PRODUCT_CATEGORY, AnyPayException.class, channelBaseService.iUtilProvider);
    }

    @NotNull
    public String getRequestId() {
        return AnyPayRandomUtil.generateNumber(32);
    }

    public String getChannelUserId(String userId) {
        var record = db.getBillingCardRecordByUserId(userId);
        return record.getChannelUserId();
    }

    /**
     * 检查是否签约过
     * @param userId 用户
     * @return 是否签约过
     */
    private boolean checkIfRegistered(String userId) {
        String cardId = db.selectBillingCardIdByUserId(userId);
        return !TextUtils.isEmpty(cardId);
    }

    public void logParams(String methodName, String... params) {
        logger.info("AnyPayChannel::" + methodName + "->params=>" + StringUtils.join(params, "|"));
    }

    private void saveCardBoundRelationship(String userId, @NotNull IBankCard card,
                                           String channelUserId, String subAccountNo
    ) {
        /*
            开始落库
            先判断交易卡是不是和结算卡同一张 防止重复插入同个cardId
         */
        var existsRecord = db.getRecordByCardId(card.getId());
        if (existsRecord != null) {
            existsRecord.setAgreementNo(subAccountNo);
            existsRecord.setBound(1); // 标志绑过卡
            db.updateByCardId(existsRecord);
        } else {
            var newRecord = AnyPayChannelBoundRelationshipDHL09Record.builder()
                    .cardId(card.getId())
                    .bound(1)
                    .isBillingCard(0)
                    .cardType(card.getCardType())
                    .userId(userId)
                    .channelUserId(channelUserId)
                    .agreementNo(subAccountNo)
                    .build();
            db.insert(newRecord);
        }
    }

    public AnyPayQuerySubAccInfoResponse querySubAccInfo(String channelUserId, String subAccountNo) {
        var requestId = getRequestId();
        var SERVICE_CODE = "10";
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", subAccountNo);
        var res = httpClient.http("querySubAccInfo",
                requestId, data, SERVICE_CODE);
        return ChannelJSONUtil.parseJson(
                ChannelJSONUtil.toJson(res.getData()), AnyPayQuerySubAccInfoResponse.class);
    }

    public AnyPayRemitResponse remitRequest(String requestId, String spOrderId, String channelUserId , String subAccountNo, String amount, String accNo, String accName, String bankCardType) {
        var SERVICE_CODE = "07";
        var orderCreateTimestamp = String.valueOf(System.currentTimeMillis());
        var notifyUrl = baseUrl + "channel/callback/" + CHANNEL_TYPE + "/remitCallback/"
                + orderCreateTimestamp + "?info=" + requestId;
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", subAccountNo);
        data.put("spOrderId", spOrderId);
        data.put("accNo", accNo);
        data.put("accName", accName);
        data.put("amount", amount);
        data.put("notifyUrl", notifyUrl);
        data.put("bankCardType", bankCardType); // * 00: 借记卡 * 01：存折 * 02：信用卡
        var res = httpClient.http("remitRequest",
                requestId, data, SERVICE_CODE);
        return ChannelJSONUtil.parseJson(
                ChannelJSONUtil.toJson(res.getData()), AnyPayRemitResponse.class);
    }

    public AnyPayActivateCardResponse activateCard(String channelUserId, String subAccountNo) {
        var requestId = getRequestId();
        var SERVICE_CODE = "03";
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", subAccountNo);
        AnyPayBaseRespModel res = httpClient.http("activateCard", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        return ChannelJSONUtil.parseJson(json,
                AnyPayActivateCardResponse.class);
    }

    /**
     * 真正判断卡绑定没的接口
     * @param card 卡
     * @return 是否绑定
     */
    private boolean checkIfBoundInner(@NotNull IBankCard card) {
        var record = db.getRecordByCardId(card.getId());
        var hasBound = !(record == null || record.getBound() == 0 || TextUtils.isEmpty(record.getAgreementNo()));
        if (!hasBound) {
            return false;
        }
        // 已经绑定过了 检查是否有未激活通道
        var isExistInactive = querySubAccInfo(record.getChannelUserId(), record.getAgreementNo()).getIsExistInactive();
        return "0".equals(isExistInactive);
    }

    @Override
    public boolean checkIfBound(@NotNull IBankCard card, boolean isRemitCard) {
        // 好还 两张都要绑定的
        return checkIfBoundInner(card);
    }

    @Override
    public void userSignUp(String userId, String province, String city, String area,
                           @NotNull IBankCard card, String payRate) {
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        var merchAddress = "广东省深圳市春南路2333号糖薰公寓4栋102号";
        var idCardNo = userIdentify.getIdCardNum();
        var name = userIdentify.getUserName();
        var mobile = card.getPhoneNumber();
        var bankName = card.getBankName();
        var idCardCode = "01";
        var debitCardCode = "00";

        try {
            var requestId = getRequestId();
            var SERVICE_CODE = "01";
            Map<String, Object> data = new TreeMap<>();
            data.put("name", name);
            data.put("cardId", idCardNo); // 身份证号
            data.put("mobile", mobile);
            data.put("province", AnyPayConstant.getProvinceCode(province));
            data.put("city", AnyPayConstant.getCityCode(city));
            data.put("merAddress", merchAddress);
            data.put("certifTp", idCardCode); // 证件类型：身份证
            data.put("accountId", card.getNumber()); // 结算卡号
            data.put("bankCardType", debitCardCode); // 卡类型：储蓄卡
            data.put("t1consFee", "100"); // 提现手续费
            data.put("t1consRate", payRate); // 消费手续费率
            data.put("bankName", bankName);
            data.put("bankCode", AnyPayConstant.getBankCode(bankName));
            data.put("productCategory", PRODUCT_CATEGORY); // 小额代还
            AnyPayBaseRespModel res = httpClient.http("userSignUp", requestId, data, SERVICE_CODE);
            var json = ChannelJSONUtil.toJson(res.getData());
            AnyPayUserSignUpResponse response = ChannelJSONUtil.parseJson(json, AnyPayUserSignUpResponse.class);
            String channelUserId = response.userId;
            var record = AnyPayChannelBoundRelationshipDHL09Record.builder()
                    .agreementNo("") // 只注册未绑定
                    .bound(0)
                    .cardId(card.getId())
                    .cardType(card.getCardType())
                    .isBillingCard(1)
                    .channelUserId(channelUserId)
                    .userId(userId)
                    .build();
            if (db.getBillingCardRecordByUserId(userId) != null) {
                // 有则改之
                db.updateByUserId(record);
            } else {
                // 无则增加
                db.insert(record);
            }
            logger.info("userSignUp success");
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "入网失败(userSignUp fail):\n"
                    + e.getMessage(), e);
        }
    }

    @Override
    public void updatePayRate(String userId, String payRate) {
        try {
            var requestId = getRequestId();
            var UPDATE_PAY_RATE_SERVICE_CODE = "15";
            var channelUserId = getChannelUserId(userId);
            Map<String, Object> data = new TreeMap<>();
            data.put("userId", channelUserId);
            data.put("productCategory", PRODUCT_CATEGORY);
            data.put("t1consFee", "100");
            data.put("t1consRate", payRate);
            httpClient.http("updatePayRate",
                    requestId, data, UPDATE_PAY_RATE_SERVICE_CODE);
            logger.info("updatePayRate success");
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE,
                    "修改费率失败(updatePayRate fail):\n" + e.getMessage(), e);
        }
    }

    @Override
    public String bindCard(String userId, @NotNull IBankCard card, String clientIP) {
        var requestId = getRequestId();
        var SERVICE_CODE = "02";
        var channelUserId = getChannelUserId(userId);
        var bankName = card.getBankName();
        var cardType = "0" + (card.getCardType() + 1); // 01储蓄 02信用
        String cvn2 = "";
        String validation = "";
        if (cardType.equals("02")) {
            cvn2 = card.getCvv2();
            validation = card.getValidation(); // MMYY
        }
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("bankCardNo", card.getNumber());
        data.put("mobile", card.getPhoneNumber());
        data.put("bankName", bankName);
        data.put("bankCode", AnyPayConstant.getBankCode(bankName));
        data.put("productCategory", PRODUCT_CATEGORY);
        data.put("sourceIp", "");
        if (cardType.equals("02")) {
            data.put("valid", validation);
            data.put("cvn2", cvn2);
        }
        AnyPayBaseRespModel res = httpClient.http("bindCard", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        AnyPayBindCardResponse anyPayBindCardResponse = ChannelJSONUtil.parseJson(json, AnyPayBindCardResponse.class);
        var subAccountNo = anyPayBindCardResponse.getSubAccountNo();

        if (anyPayBindCardResponse.getStatus() == 1) {
            // 已经激活过了
            saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
            logger.info("bindCard success: CardAlreadyBoundException(REPEAT_CARD_BINDING)");
//            throw new CardAlreadyBoundException();
        }

        // 激活
        AnyPayActivateCardResponse anyPayActivateCardResponse =
                activateCard(channelUserId, subAccountNo);
//        if (anyPayActivateCardResponse.getStatus() == 0) {
//            // 未能激活成功
//            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "绑卡失败(bindCard fail): 未能激活");
//            return null; // @unreachable
//        }

        // 检查需不需要短信
        var verifyType = anyPayActivateCardResponse.getVerifyType();
        if (verifyType == AnyPayActivateCardResponse.NO_NEED_SMS) {
            // 直接激活成功
            saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
            logger.info("bindCard success: CardAlreadyBoundException(NO_NEED_SMS)");
            throw new CardAlreadyBoundException();
        }

        var serialNo = anyPayActivateCardResponse.getSerialNo(); // 绑卡token
        var uuid = UUID.randomUUID().toString();
        AnyPayBindCardPayload payload = AnyPayBindCardPayload.builder()
                .userId(userId)
                .card(card)
                .clientIP(clientIP)
                .requestId(requestId)
                .subAccountNo(subAccountNo)
                .serialNo(serialNo)
                .channelUserId(channelUserId)
                .build();
        channelBaseService.redis.boundValueOps(BINDCARD_SMS_PREFIX + uuid).set(
                ChannelJSONUtil.toJson(payload), ChannelConstant.ONE_DAY, TimeUnit.MILLISECONDS);

        logger.info("bindCard success");
        return uuid; // 返回绑卡token
    }

    @Override
    public String bindCardConfirm(String smsCode, String userId, String smsUUID) {
        String valStr = channelBaseService.redis.boundValueOps(BINDCARD_SMS_PREFIX + smsUUID).get();
        if (valStr == null || valStr.isEmpty()) {
            logger.fail(AnyPayConstant.SMS_EXPIRED, "短信已过期!");
        }
        AnyPayBindCardPayload payload = ChannelJSONUtil.parseJson(valStr, AnyPayBindCardPayload.class);

        var requestId = getRequestId();
        var SERVICE_CODE = "04";
        var card = payload.getCard();
        var channelUserId = payload.getChannelUserId();
        var subAccountNo = payload.getSubAccountNo();
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", userId);
        data.put("subAccountNo", subAccountNo);
        data.put("smsCode", smsCode);
        data.put("serialNo", payload.getSerialNo());
        AnyPayBaseRespModel res = httpClient.http("bindCardConfirm", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        var resData = ChannelJSONUtil.parseJson(json, AnyPayBindCardConfirmResponse.class);
        if (resData.getStatus() == 0) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "绑卡失败(bindCardConfirm fail): 未能激活");
            return null; // @unreachable
        }
        saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
        return card.getId();
    }

    @Override
    public String tryBindCard(String userId, IBankCard card, String clientIP) {
        var location = channelBaseService.channelLocationService.getLocation();
        var province = location.getProvince();
        var city = location.getCity();
        var area = location.getArea();
        var payRate = "0.55";
        return tryBindCard(userId, card, clientIP, payRate, province, city, area);
    }

    @Override
    public String tryBindCard(String userId, @NotNull IBankCard card, String clientIP,
                              String payRate, String province, String city, String area) {
        boolean registered = this.checkIfRegistered(userId);
        int cardType = card.getCardType(); // 0 - 储蓄卡, 1 - 信用卡
        if (!registered) {
            if (cardType == 1) {
                // 未签约且是绑定信用卡 找一张储蓄卡来签约再绑卡
                var signUpCardTmp = channelBaseService.iUserBankCardProvider.getUserDebitCard(userId);
                // 这样拿到的卡是信息不完整的(比如说卡号打了🐴) 需要拿cardId换完整的卡
                var cardId = signUpCardTmp.getId();
                var signUpCard = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
                this.userSignUp(userId, province, city, area, signUpCard, payRate);
                return this.bindCard(userId, card, clientIP);
            }
            // 未签约且是绑定储蓄卡直接签约
            this.userSignUp(userId, province, city, area, card, payRate);
        }
        // 无论储蓄卡还是消费卡都绑定一下
        return this.bindCard(userId, card, clientIP);
    }

    @Override
    public String pay(String userId, @NotNull IBankCard payCard, IBankCard remitCard, @NotNull BigDecimal orderAmount,
                      BigDecimal userFeeAmount, String clientIP,
                      String province, String city, String area) throws PayChannelDeadException {
        var requestId = getRequestId();
        var SERVICE_CODE = "06";
        var spOrderId = UUID.randomUUID().toString().replaceAll("-", ""); // 订单号由这边生成
        var channelUserId = getChannelUserId(userId);
        var record = db.getRecordByCardId(payCard.getId());
        if (record == null) {
            logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG, "请先绑定支付卡");
        }
        var orderCreateTimestamp = String.valueOf(System.currentTimeMillis());
        String callbackUrl = baseUrl + "channel/callback/" + CHANNEL_TYPE
                + "/payCallback/" + orderCreateTimestamp + "?info=" + requestId;
        var location = channelBaseService.channelLocationService.getLocation();

        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", record.getAgreementNo());
        data.put("amount", orderAmount.multiply(BigDecimal.valueOf(100L)).longValue() + "");
        data.put("spOrderId", spOrderId);
        data.put("notifyUrl", callbackUrl);
        data.put("province", AnyPayConstant.getProvinceCode(location.getProvince()));
        data.put("city", AnyPayConstant.getCityCode(location.getCity()));

        data.put("sourceIp", "");

        var res = httpClient.http("pay",
                requestId, data, SERVICE_CODE);
        var response = ChannelJSONUtil.parseJson(
                ChannelJSONUtil.toJson(res.getData()), AnyPayPayResponse.class);
        var SUCCESS = "success";
        var FAIL = "fail";
        var WAITING = "waiting";
        var status = response.getStatus();
        var vipType = channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType();
        if (FAIL.equals(status)) {
            ExecutionFlow failFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .status(ExecutionFlow.FlowStatus.FAILED) // 在回调中改状态
                    .type(ExecutionFlow.Type.PAY)
                    .channelType(CHANNEL_TYPE)
                    .amount(orderAmount)
                    .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("request", ChannelJSONUtil.toJson(data));
                        put("response", ChannelJSONUtil.toJson(res));
                    }}))
                    .cardId(payCard.getId())
                    .requestId(requestId)
                    .commission(userFeeAmount)
                    .remitFee(BigDecimal.ZERO)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .failReason("")
                    .build();
            String failId = channelBaseService.flowService.add(failFlow);
            logger.fail(ChannelConstant.PAY_ERROR,
                    "支付失败(pay failed): spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
        }
        if (WAITING.equals(status) || SUCCESS.equals(status)) {
            ExecutionFlow executionFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .status(WAITING.equals(status) ? ExecutionFlow.FlowStatus.INITIALIZED
                            : ExecutionFlow.FlowStatus.SUCCEEDED)
                    .type(ExecutionFlow.Type.PAY)
                    .channelType(CHANNEL_TYPE)
                    .amount(orderAmount)
                    .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("request", ChannelJSONUtil.toJson(data));
                        put("response", ChannelJSONUtil.toJson(res));
                    }}))
                    .cardId(payCard.getId())
                    .requestId(requestId)
                    .commission(userFeeAmount)
                    .remitFee(BigDecimal.ZERO)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .build();
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("pay success with flow id：" + executionFlowId);
            return executionFlowId;
        }
        return null;  // @unreachable
    }

    @Override
    public String tryPay(String userId, IBankCard payCard, IBankCard remitCard, BigDecimal orderAmount,
                         BigDecimal userFeeAmount, String clientIP, String payRate,
                         String province, String city, String area)
            throws TryingBindCardException, PayChannelDeadException {
        logParams("tryPay", userId, ChannelJSONUtil.toJson(payCard),  ChannelJSONUtil.toJson(remitCard),
                orderAmount.toString(), userFeeAmount.toString(), clientIP, payRate, province, city, area);
        // 业务方的费率是以%为单位的 如5%就是5 anypay要求以1为单位 所以这里要除以100
        String _payRate = BigDecimal.valueOf(Double.parseDouble(payRate)).divide(BigDecimal.valueOf(100L)).toString();
//        // 因为绑卡不需要验证 所以暂时不管payload重放逻辑
//        if (!checkIfBoundInner(payCard)) {
//            this.tryBindCard(userId, payCard, clientIP, _payRate, province, city, area);
//        }
        // 此通道仅作好还 因此它的tryPay remitCard为null remitCard需要在remit时绑定
//        if (!checkIfBoundInner(remitCard)) {
//            this.tryBindCard(userId, remitCard, clientIP, _payRate, province, city, area);
//        }
        try {
            this.updatePayRate(userId, _payRate);
            return this.pay(
                    userId,
                    payCard, remitCard,
                    orderAmount, userFeeAmount,
                    clientIP,
                    province, city, area
            );
        } catch (PayChannelDeadException e) {
            logger.errorInfo(e.getMessage(), e);
            e.setPayRate(payRate); // 补全pay方法里没能写入的信息
            throw e; // 抛出给dispatcher处理
        }
    }

    @Override
    public String verifyPay(String userId, String smsCode, TryPayPayload payload)
            throws TryingBindCardException, PayChannelDeadException {
        return null;
    }

    @Override
    public String remit(String userId, IBankCard remitCard, @NotNull IBankCard payCard, BigDecimal amount,
                        BigDecimal userFeeAmount, boolean active,
                        TryBindCardExtendInfo tryBindCardExtendInfo) throws RemitCardUnboundException {
        logParams("remit", userId, ChannelJSONUtil.toJson(remitCard),  ChannelJSONUtil.toJson(payCard),
                amount.toString(), userFeeAmount.toString(), active + "", ChannelJSONUtil.toJson(tryBindCardExtendInfo));
        var SUCCESS = "success";
        var FAIL = "fail";
        var WAITING = "waiting";
        var record = db.getRecordByCardId(payCard.getId());
        var channelUserId = getChannelUserId(userId);
        if (record == null) {
            logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG, "传入支付卡不存在(payCard for remit is null)");
            return ""; // @unreachable
        }
        var requestId = getRequestId();
        var spOrderId = UUID.randomUUID().toString().replaceAll("-", "");
        var subAccountNo = record.getAgreementNo();
        if (TextUtils.isEmpty(subAccountNo)) {
            logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG, "传入支付卡未绑定(payCard for remit unbound)");
        }
        var vipType = channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType();
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        var name = userIdentify.getUserName();

        var targetAmount = amount.add(userFeeAmount).multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.FLOOR).intValue(); // 传 目标出款金额 + 手续费
        var balance = querySubAccInfo(channelUserId, subAccountNo).getBalanceAmt(); // 余额
        if (balance < targetAmount) {
            logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG,
                    "传入支付卡余额不足(payCard for remit is insufficient balance)");
        }

        var bankCardType = remitCard.getCardType() == 0 ? "00" : "02"; // 00储蓄 02信用

        var res = remitRequest(requestId, spOrderId, channelUserId,
                subAccountNo, targetAmount + "", remitCard.getNumber(), name, bankCardType);
        var status = res.getStatus();
        if (WAITING.equals(status) || SUCCESS.equals(status)) {
            ExecutionFlow executionFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .type(active ? ExecutionFlow.Type.ACTIVE_REMIT : ExecutionFlow.Type.REMIT)
                    .status(WAITING.equals(status) ? ExecutionFlow.FlowStatus.INITIALIZED
                            : ExecutionFlow.FlowStatus.SUCCEEDED)
                    .channelType(CHANNEL_TYPE)
                    .amount(amount)
                    .extendInfo(ChannelJSONUtil.toJson(res))
                    .cardId(remitCard.getId())
                    .requestId(requestId)
                    .commission(BigDecimal.ZERO)
                    .remitFee(userFeeAmount)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .build();
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("remit订单提交成功 流水id：" + executionFlowId);
            return executionFlowId;
        }
        if (FAIL.equals(status)) {
            ExecutionFlow flow = ExecutionFlow.builder()
                    .userId(userId)
                    .type(active ? ExecutionFlow.Type.ACTIVE_REMIT : ExecutionFlow.Type.REMIT)
                    .status(ExecutionFlow.FlowStatus.FAILED)
                    .channelType(CHANNEL_TYPE)
                    .amount(amount)
                    .extendInfo(ChannelJSONUtil.toJson(res))
                    .cardId(remitCard.getId())
                    .requestId(requestId)
                    .commission(BigDecimal.ZERO)
                    .remitFee(userFeeAmount)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .build();
            channelBaseService.iNotifyProvider.orderStatusNotify(flow, INotifyProvider.OrderStatus.FAILED); // 通知业务失败
            return channelBaseService.flowService.add(flow);
        }
        return null;
    }

    @Override
    public void handleCallback(String callBackAction, HttpServletRequest request,
                               HttpServletResponse response, String orderCreateTimestamp, String info) {
        var createTime = new Date(Long.parseLong(orderCreateTimestamp));
        var SUCCESS = "success";
        var FAIL = "fail";
        var requestId = info; // requestId放在了qs里
        if (AnyPayConstant.CALLBACK_PAY.equals(callBackAction)) {
            logger.info("于" + orderCreateTimestamp + "收到支付回调, requestId => " + requestId);
            try {
                String json = IOUtils.toString(request.getReader());
                logger.info("callback body json: " + json);
                AnyPayBaseRespModel model = httpClient.getResp(json, true);
                var dataJson = ChannelJSONUtil.toJson(model.getData());
                logger.info("callback data json: " + dataJson);
                var res = ChannelJSONUtil.parseJson(
                        dataJson,
                        AnyPayCallbackResponse.class
                );
                var orderId = res.getSpOrderId();
                if (FAIL.equals(res.getStatus())) {
                    var reason = res.getRespMsg();
                    logger.info("orderId: " + orderId + "支付失败 => " + reason);
                    // 查询原流水
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    ExecutionFlow targetFlow = channelBaseService.flowService.getByRequestIdAndCreateTime(executionFlow);
                    // 更新extendInfo
                    var requestCtx = targetFlow.getExtendInfo(); // 原本只有流水信息
                    var finalReason = reason; // 内部类访问 需要final
                    var newInfo = ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("reason", finalReason);
                        put("requestCtx", requestCtx);
                    }});
                    executionFlow.setFailReason(finalReason);
                    // 对失败状态和extendInfo落库 并通知业务
                    ExecutionFlow failFlow = channelBaseService.flowService.fail(executionFlow, newInfo);
                    if (failFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(failFlow, INotifyProvider.OrderStatus.FAILED);
                } else if (SUCCESS.equals(res.getStatus())) {
                    logger.info("orderId: " + orderId + "支付成功");
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    // 根据requestId和createTime落库 并通知业务
                    ExecutionFlow successFlow = channelBaseService.flowService.success(executionFlow);
                    if (successFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(successFlow, INotifyProvider.OrderStatus.SUCCEED);
                }
                response.getWriter().write(ChannelConstant.SUCCESS);
            } catch (IOException e) {
                logger.info("pay 回调异常：" + e.toString());
            }
        }
        if (AnyPayConstant.CALLBACK_REMIT.equals(callBackAction)) {
            logger.info("于" + orderCreateTimestamp + "收到代付回调, requestId => " + requestId);
            try {
                String json = IOUtils.toString(request.getReader());
                logger.info("callback body json: " + json);
                AnyPayBaseRespModel model = httpClient.getResp(json, true);
                var dataJson = ChannelJSONUtil.toJson(model.getData());
                logger.info("callback data json: " + dataJson);
                var res = ChannelJSONUtil.parseJson(
                        dataJson,
                        AnyPayCallbackResponse.class
                );
                var orderId = res.getSpOrderId();
                if (FAIL.equals(res.getStatus())) {
                    var reason = res.getRespMsg();
                    logger.info("orderId: " + orderId + "代付失败 => " + reason);
                    // 查询原流水
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    // 对失败状态和extendInfo落库 并通知业务
                    ExecutionFlow failFlow = channelBaseService.flowService.fail(executionFlow, reason);
                    if (failFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单(可能是该订单已经成功)");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(failFlow, INotifyProvider.OrderStatus.FAILED);
                } else if (SUCCESS.equals(res.getStatus())) {
                    logger.info("orderId: " + orderId + "代付成功");
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    // 根据requestId和createTime落库 并通知业务
                    ExecutionFlow successFlow = channelBaseService.flowService.success(executionFlow);
                    if (successFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单(可能是该订单已经成功)");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(successFlow, INotifyProvider.OrderStatus.SUCCEED);
                }
                response.getWriter().write(SUCCESS);
            } catch (IOException e) {
                logger.info("pay 回调异常：" + e.toString());
            }
        }
    }

    @Override
    public ArrayList<ChannelBalance> getBalance(String userId) {
        ArrayList<ChannelBalance> list = new ArrayList<>();
        boolean registered = this.checkIfRegistered(userId);
        if (!registered) {
            // 未签约
            logger.info("未签约通道，余额-1");
            list.add(ChannelBalance.builder()
                    .channelName(CHANNEL_NAME)
                    .channelType(CHANNEL_TYPE)
                    .balance(BigDecimal.valueOf(-1))
                    .build());
            return list;
        }

        var records = db.getAllRecordByUserId(userId);
        records.forEach(record -> {
            var subAccountNo = record.getAgreementNo();
            if (!TextUtils.isEmpty(subAccountNo)) {
                var balance = querySubAccInfo(getChannelUserId(userId), subAccountNo).getBalanceAmt();
                list.add(ChannelBalance.builder()
                        .channelName(CHANNEL_NAME)
                        .channelType(CHANNEL_TYPE + "$" + record.getCardId()) // 特殊逻辑 拿channelType当作extendInfo 记录这张卡的信息
                        .balance(BigDecimal.valueOf(balance).divide(BigDecimal.valueOf(100)))
                        .build());
            }
        });
        return list;
    }
}
